/*
 * BlackLightning.Module
 * Andy Tidball
 * 
 * Project: Black Lightning
 * Copyright: GNU General Public License
 */

using System;
using BlackLightning.PluginLib;
using BlackLightning.PluginLib.Modules;
using BlackLightning.PluginLib.Modules.Packets;
using BLLib.Configuration;

namespace BlackLightning {
	/// <summary>
	/// Wraps around loaded external modules and provides additional functionality for the rest of Black Lightning.
	/// </summary>
	internal class Module : MarshalByRefObject, IModule {
		// constants
		public const string FullnameSeparator = "."; // the separator used between a plugin's name and the module's name in its full name

		// constants - options
		private const string OptionCategory                 = "Black Lightning";
		public  const string LoadAtStartupOptionName        = "Load at Startup";
		private const string LoadAtStartupOptionDescription = "Whether or not to load this module immediately when connecting to the game.";
		private const bool   LoadAtStartupOptionDefault     = false;

		// member data
		private Plugin      _Plugin;  // the plugin that owns this module
		private ModuleProxy _Proxy;   // the proxy in the plugin's app domain that we delegate most of our work to
		private string      _Name;    // the module's name
		private OptionSet   _Options; // the module's options

		/// <summary>
		/// Creates a new Module.
		/// </summary>
		/// <param name="Owner">The plugin that will own this module.</param>
		/// <param name="Proxy">The module proxy to use to communicate with the module.</param>
		public Module(Plugin Owner, ModuleProxy Proxy) {
			// save our data
			_Plugin  = Owner;
			_Proxy   = Proxy;
			_Name    = Proxy.Name;

			// wire our options to let us know when a value changes
			_Options = _Plugin.Options.GetOrCreateOptionSet(_Name);
			_Options.IconIndex = BlackLightning.GearIconIndex;
			_Options.SelectedIconIndex = BlackLightning.GearIconIndex;
			_Options.OptionValueChanged += OptionValueChanged;

			// wire the proxy's events to fire ours
			_Proxy.SendMessage += MessageReceived;
			_Proxy.SendPacket  += PacketReceived;
		}

		/// <summary>
		/// Creates a new Module that is internal to Black Lightning.
		/// </summary>
		/// <param name="Proxy">The proxy to use to communicate with the module.</param>
		/// <param name="MessageHandler">The handler to receive our messages.</param>
		/// <param name="PacketHandler">The handler to receive our packets.</param>
		public Module(ModuleProxy Proxy, SendMessageEventHandler MessageHandler, SendPacketEventHandler PacketHandler) {
			// save our data
			_Plugin = null;
			_Proxy  = Proxy;
			_Name   = Proxy.Name;

			// modules without plugins don't get options
			_Options = null;

			// wire the proxy's events to fire ours
			_Proxy.SendMessage += MessageReceived;
			_Proxy.SendPacket  += PacketReceived;

			// wire our events to fire the given handlers
			this.SendMessage += MessageHandler;
			this.SendPacket  += PacketHandler;
		}

		/// <summary>
		/// Gets the plugin that owns this module.
		/// </summary>
		public Plugin Plugin {
			get {
				return _Plugin;
			}
		}

		/// <summary>
		/// Gets the name of the module.
		/// </summary>
		public string Name {
			get {
				return _Name;
			}
		}

		/// <summary>
		/// Gets the full name of the module, including the plugin's name.
		/// </summary>
		public string Fullname {
			get {
				if (Plugin == null) {
					return "BlackLightning" + Module.FullnameSeparator + Name;
				} else {
					return Plugin.Name + Module.FullnameSeparator + Name;
				}
			}
		}

		/// <summary>
		/// Gets the module's options.
		/// </summary>
		public OptionSet Options {
			get {
				return _Options;
			}
		}

		/// <summary>
		/// Gets the module's help text.
		/// </summary>
		public string HelpText {
			get {
				return GetProperty(ModuleProperty.HelpText) as string;
			}
		}

		/// <summary>
		/// Gets whether or not the Module wants to receive OptionValueChangedMessages for internal options related to it.
		/// </summary>
		public bool ReceiveInternalOptionUpdates {
			get {
				bool? Receive = GetProperty(ModuleProperty.ReceiveInternalOptionUpdates) as bool?;
				if (Receive == null) {
					return false;
				} else {
					return Receive.Value;
				}
			}
		}

		/// <summary>
		/// Fired whenever the underlying module fires its SendMessage event.
		/// </summary>
		public event SendMessageEventHandler SendMessage;

		/// <summary>
		/// Fires the SendMessage event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		private void OnSendMessage(SendMessageEventArgs e) {
			if (SendMessage != null) {
				SendMessage(this, e);
			}
		}

		/// <summary>
		/// Fired whenever the underlying module fires its SendPacket event.
		/// </summary>
		public event SendPacketEventHandler SendPacket;

		/// <summary>
		/// Fires the SendPacket event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		private void OnSendPacket(SendPacketEventArgs e) {
			if (SendPacket != null) {
				SendPacket(this, e);
			}
		}

		/// <summary>
		/// Gets the value of the given property from the module.
		/// </summary>
		/// <param name="Property">The property whose value to retrieve from the module.</param>
		/// <returns>The value of the requested property.</returns>
		public object GetProperty(ModuleProperty Property) {
			return _Proxy.GetProperty(Property);
		}

		/// <summary>
		/// Passes the given message down to the underlying module.
		/// </summary>
		/// <param name="Message">The message to pass to the underlying module.</param>
		public void HandleMessage(Message Message) {
			_Proxy.HandleMessage(Message);

			// if this is our loaded message, register our own options
			// we do this here so that the module can't get option value changed messages before their load message
			if (_Options != null && Message is LoadedMessage) {
				_Options.GetOrCreateOption(LoadAtStartupOptionName).Initialize(new OptionInitializer(LoadAtStartupOptionDefault.GetType(), LoadAtStartupOptionDefault, LoadAtStartupOptionDescription, OptionCategory, BlackLightning.GlobalOptionValueIndex, BlackLightning.UserOptionValueIndex, BlackLightning.CharacterOptionValueIndex));
			}
		}

		/// <summary>
		/// Passes the given packet down to the underlying module.
		/// </summary>
		/// <param name="Packet">The packet to pass to the underlying module.</param>
		public void HandlePacket(ref BLPacket Packet) {
			// check if our plugin wants to handle this packet first
			if (_Plugin != null) {
				bool? PluginWantsPacket = (bool?)_Plugin.GetProperty(PluginProperty.ReceiveModulePackets);
				if (PluginWantsPacket.HasValue && PluginWantsPacket.Value) {
					_Plugin.HandlePacket(ref Packet);
				}
			}

			// handle the packet down to the module itself
			_Proxy.HandlePacket(ref Packet);
		}

		/// <summary>
		/// Unloads the module.
		/// </summary>
		public void Unload() {
			// unload the module
			_Proxy.Unload();

			// stop listening for option value changes and uninitialize all of our options
			if (_Options != null) {
				_Options.OptionValueChanged -= OptionValueChanged;
				_Options.UninitiazeAll(false);
			}

			// unwire the underlying module's events from ours
			_Proxy.SendMessage -= MessageReceived;
			_Proxy.SendPacket  -= PacketReceived;
		}

		/// <summary>
		/// Unloads the internal Black Lightning module.
		/// </summary>
		/// <param name="MessageHandler">The handler to unwire from our SendMessage event.</param>
		/// <param name="PacketHandler">The handler to unwire from our SendPacket event.</param>
		public void Unload(SendMessageEventHandler MessageHandler, SendPacketEventHandler PacketHandler) {
			// use the other unload first
			Unload();

			// unwire the given handlers from our events
			this.SendMessage -= MessageHandler;
			this.SendPacket  -= PacketHandler;
		}

		/// <summary>
		/// Called when the LeaseManager needs to acquire a Lease for how long we'll live on by ourselves.
		/// </summary>
		/// <returns>A Lease that will be used to control our lifetime.</returns>
		public override object InitializeLifetimeService() {
			// return null to signal an infinite amount of time
			return null;
		}

		/// <summary>
		/// Handles the underlying module's SendMessage event.
		/// </summary>
		/// <param name="Sender">The underlying module.</param>
		/// <param name="e">Arguments about the event.</param>
		private void MessageReceived(object Sender, SendMessageEventArgs e) {
			if (e.Message is RegisterOptionMessage) {
				// this is an option registration, we can handle that
				MessageReceived_RegisterOption(e.Message as RegisterOptionMessage);
			} else if (e.Message is UnregisterOptionMessage) {
				// this is an option unregistration, we can handle that
				MessageReceived_UnregisterOption(e.Message as UnregisterOptionMessage);
			} else if (e.Message is ChangeOptionValueMessage) {
				// they want to change an option's value, we can handle that
				MessageReceived_ChangeOptionValue(e.Message as ChangeOptionValueMessage);
			} else {
				// fire the event further upwards
				OnSendMessage(e);
			}
		}

		/// <summary>
		/// Handles the underlying module sending a RegisterOptionMessage.
		/// </summary>
		/// <param name="Message">The message sent by the underlying module.</param>
		private void MessageReceived_RegisterOption(RegisterOptionMessage Message) {
			// make sure they're not using the same category we use for our own options
			if (Message.Category == OptionCategory) {
				HandleMessage(new ErrorMessage(Message, new InvalidOperationException("The category '" + OptionCategory + "' is reserved for internal use.")));
				return;
			}

			// initialize the option as per the message
			Option O = _Options.GetOrCreateOption(Message.Name);
			O.Initialize(Message.GetInitializer(new int[]{BlackLightning.GlobalOptionValueIndex, BlackLightning.UserOptionValueIndex, BlackLightning.CharacterOptionValueIndex}));
		}

		/// <summary>
		/// Handles the underlying module sending a RegisterOptionMessage.
		/// </summary>
		/// <param name="Message">The message sent by the underlying module.</param>
		private void MessageReceived_UnregisterOption(UnregisterOptionMessage Message) {
			// make sure that we have an option by that name
			if (!_Options.HasOption(Message.Name)) {
				// send an error message to the module
				HandleMessage(new ErrorMessage(Message, new ArgumentException("There is no registered option with the given name.")));
				return;
			}

			// remove the option
			_Options.RemoveOption(Message.Name);
		}

		/// <summary>
		/// Handles the underlying module sending a ChangeOptionValueMessage.
		/// </summary>
		/// <param name="Message">The message sent by the underlying module.</param>
		private void MessageReceived_ChangeOptionValue(ChangeOptionValueMessage Message) {
			// make sure that we actually have an option by that name
			if (!_Options.HasOption(Message.Name)) {
				// send an error message to the module
				HandleMessage(new ErrorMessage(Message, new ArgumentException("There is no registered option with the given name.")));
				return;
			}

			try {
				// grab the option and make sure it's not one of ours
				Option O = _Options.GetOption(Message.Name);
				if (O.Category == OptionCategory) {
					throw new InvalidOperationException("Options in the category '" + OptionCategory + "' are reserved for internal use.");
				}

				// set the option's value
				O.Value = Message.Value;
				O.ApplyNewValue();
			} catch (Exception x) {
				// send an error message to the module
				HandleMessage(new ErrorMessage(Message, x));
			}
		}

		/// <summary>
		/// Handles the underlying module's SendPacket event.
		/// </summary>
		/// <param name="Sender">The underlying module.</param>
		/// <param name="e">Arguments about the event.</param>
		private void PacketReceived(object Sender, SendPacketEventArgs e) {
			// fire our own SendPacket event
			OnSendPacket(e);
		}
		
		/// <summary>
		/// Handles any of the module's option's value changing.
		/// </summary>
		/// <param name="Sender">The option whose value changed.</param>
		/// <param name="e">Arguments about the event.</param>
		private void OptionValueChanged(object Sender, OptionValueChangedEventArgs e) {
			// unless this is the lowest level option, we don't care
			Option O = Sender as Option;
			if (e.ValueIndex != O.LowestValueIndex) {
				return;
			}

			// check if this is one of our internal options
			if (O.Category == OptionCategory) {
				// if they don't want these messages, return now
				if (!ReceiveInternalOptionUpdates) {
					return;
				}
			}

			// let the module know that an option value changed
			HandleMessage(new OptionValueChangedMessage(O.Name, e.OldValue, e.NewValue));
		}
	}

	/// <summary>
	/// Represents a loaded module in its plugin's app domain.
	/// </summary>
	internal class ModuleProxy : MarshalByRefObject, IModule {
		// member data
		private IModule _Module; // the external module

		/// <summary>
		/// Creates a new ModuleProxy.
		/// </summary>
		/// <param name="Module">The external IModule that the proxy will represent.</param>
		public ModuleProxy(IModule Module) {
			// save our data
			_Module = Module;

			// wire the module's events to fire ours
			_Module.SendMessage += MessageReceived;
			_Module.SendPacket  += PacketReceived;
		}

		/// <summary>
		/// Gets the name of the underlying module.
		/// </summary>
		public string Name {
			get {
				Type T = _Module.GetType();
				object[] Attributes = T.GetCustomAttributes(typeof(ModuleNameAttribute), true);
				if (Attributes.Length == 0) {
					return T.Name;
				} else {
					return ((ModuleNameAttribute)Attributes[0]).Name;
				}
			}
		}
		
		/// <summary>
		/// Fired whenever the underlying module fires its SendMessage event.
		/// </summary>
		public event SendMessageEventHandler SendMessage;

		/// <summary>
		/// Fires the SendMessage event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		private void OnSendMessage(SendMessageEventArgs e) {
			if (SendMessage != null) {
				SendMessage(this, e);
			}
		}

		/// <summary>
		/// Fired whenever the underlying module fires its SendPacket event.
		/// </summary>
		public event SendPacketEventHandler SendPacket;

		/// <summary>
		/// Fires the SendPacket event.
		/// </summary>
		/// <param name="e">Arguments to pass to the event.</param>
		private void OnSendPacket(SendPacketEventArgs e) {
			if (SendPacket != null) {
				SendPacket(this, e);
			}
		}

		/// <summary>
		/// Gets the value of the given property from the module.
		/// </summary>
		/// <param name="Property">The property whose value to retrieve from the module.</param>
		/// <returns>The value of the requested property.</returns>
		public object GetProperty(ModuleProperty Property) {
			try {
				return _Module.GetProperty(Property);
			} catch (Exception x) {
				OnSendPacket(new SendPacketEventArgs(new ExceptionPacket(x)));
				return null;
			}
		}

		/// <summary>
		/// Passes the given message down to the underlying module.
		/// </summary>
		/// <param name="Message">The message to pass to the underlying module.</param>
		public void HandleMessage(Message Message) {
			try {
				_Module.HandleMessage(Message);
			} catch (Exception x) {
				OnSendPacket(new SendPacketEventArgs(new ExceptionPacket(x)));
			}
		}

		/// <summary>
		/// Passes the given packet down to the underlying module.
		/// </summary>
		/// <param name="Packet">The packet to pass to the underlying module.</param>
		public void HandlePacket(ref BLPacket Packet) {
			try {
				_Module.HandlePacket(ref Packet);
			} catch (Exception x) {
				OnSendPacket(new SendPacketEventArgs(new ExceptionPacket(x)));
			}
		}

		/// <summary>
		/// Unloads the underlying module.
		/// </summary>
		public void Unload() {
			// unwire the module's events from firing ours
			_Module.SendMessage -= MessageReceived;
			_Module.SendPacket  -= PacketReceived;
		}

		/// <summary>
		/// Called when the LeaseManager needs to acquire a Lease for how long we'll live on by ourselves.
		/// </summary>
		/// <returns>A Lease that will be used to control our lifetime.</returns>
		public override object InitializeLifetimeService() {
			// return null to signal an infinite amount of time
			return null;
		}

		/// <summary>
		/// Handles the underlying module's SendMessage event.
		/// </summary>
		/// <param name="Sender">The underlying module.</param>
		/// <param name="e">Arguments about the event.</param>
		private void MessageReceived(object Sender, SendMessageEventArgs e) {
			// fire our own SendMessage event
			OnSendMessage(e);
		}

		/// <summary>
		/// Handles the underlying module's SendPacket event.
		/// </summary>
		/// <param name="Sender">The underlying module.</param>
		/// <param name="e">Arguments about the event.</param>
		private void PacketReceived(object Sender, SendPacketEventArgs e) {
			// fire our own SendPacket event
			OnSendPacket(e);
		}
	}
}